/* 
 * Cashbox is the legal property of its developers, whose names are listed in the copyright file included
 * with this source distribution.
 * 
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
 * General Public License as published by the Free Software Foundation; either version 2 of the License,
 * or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
 * Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this program; if not,
 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#import <Cashbox/CBAccount.h>
#import <Cashbox/CBTransaction.h>
#import <Cashbox/CBAttribute.h>
#import <Cashbox/CBObject.h>
#import <Cashbox/CBDataControllerProtocol.h>

static void *CBInternalTotalChangeContext = @"CBInternalTotalChangeContext";
static void *CBTransactionMutationChangeContext = @"CBTransactionMutationChangeContext";

@interface CBAccount (Private)
- (void)addObservers;
- (void)retotalFrom:(NSDate *)from to:(NSDate *)to;
@end

@implementation CBAccount

+ (NSArray *)accountsWithAnyAccountType:(NSArray *)array {
	NSMutableArray *result = [NSMutableArray array];
	NSEnumerator *typeEnumerator = [array objectEnumerator];
	Class type;
	while ((type = [typeEnumerator nextObject])) {
		NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
		[request setEntity:[type entity]];
		[result addObjectsFromArray:
		 [[[[CBObject sharedCashboxInstance] dataController] managedObjectContext] executeFetchRequest:request
																								 error:NULL]];
	}
	
	NSSortDescriptor *descriptor = 
		[[[NSSortDescriptor alloc] initWithKey:@"name"
									 ascending:YES
									  selector:@selector(caseInsensitiveCompare:)] autorelease];
	return [result sortedArrayUsingDescriptors:[NSArray arrayWithObject:descriptor]];
}

+ (CBAccount *)accountWithName:(NSString *)name {
	NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
	[request setEntity:[[self class] entity]];
	[request setPredicate:[NSPredicate predicateWithFormat:@"name == %@", name]];
	[request setSortDescriptors:
	 [NSArray arrayWithObject:
	  [[[NSSortDescriptor alloc] initWithKey:@"name"
								   ascending:YES
									selector:@selector(caseInsensitiveCompare:)] autorelease]]];
	[request setFetchLimit:1];
	return [[[[[CBObject sharedCashboxInstance] dataController] managedObjectContext]
			 executeFetchRequest:request error:NULL] firstObject];
}

+ (NSArray *)roles {
	NSMutableArray *result = [NSMutableArray array];
	for (CBAttribute *attribute in [[[(CBAccount <CBAccount> *)self transactionClass] attributeInfo] attributes]) {
		if ([attribute role] != CBNoRole) {
			[result addObject:[NSNumber numberWithInt:[attribute role]]];
		}
	}
	return [result copy];
}

- (void)awakeFromInsert {
	[super awakeFromInsert]; // must be first
	[self setNumber:0];
	[self addObservers];
}

- (void)awakeFromFetch {
	[super awakeFromFetch]; // must be first
	[self addObservers];
}

- (void)addObservers {
	[self addObserver:self
		   forKeyPath:@"transactions"
			  options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionInitial
			  context:CBTransactionMutationChangeContext];
}


#pragma mark getters/setters
// ----------------------------------------------------------------------------------------------------
// getters/setters
// ----------------------------------------------------------------------------------------------------

@dynamic name;
@dynamic number;
@dynamic additionalInfo;
@dynamic transactions;

- (void)addTransactionObservers:(NSSet *)transactions {
	for (CBTransaction *t in transactions) {
		[t addObserver:self
			forKeyPath:@"date"
			   options:NSKeyValueObservingOptionOld
			   context:CBInternalTotalChangeContext];
		[t addObserver:self
			forKeyPath:@"internalTotal"
			   options:NSKeyValueObservingOptionOld
			   context:CBInternalTotalChangeContext];
	}
}

- (void)removeTransactionObservers:(NSSet *)transactions {
	for (CBTransaction *t in transactions) {
		[t removeObserver:self
			   forKeyPath:@"date"];
		[t removeObserver:self
			   forKeyPath:@"internalTotal"];
	}
}

- (void)observeValueForKeyPath:(NSString *)keyPath
					  ofObject:(id)object
						change:(NSDictionary *)change
					   context:(void *)context {
	if (context == CBInternalTotalChangeContext) {		
		if (![object isFault]) {
			if ([keyPath isEqualToString:@"date"]) {
				NSDate *originalValue = [change valueForKey:NSKeyValueChangeOldKey];
				NSDate *changedValue = [object date];
				
				if (originalValue == (id)[NSNull null]) { originalValue = nil; }
				
				id from = [originalValue earlierDate:changedValue];
				id to = [originalValue laterDate:changedValue];
				[[object account] retotalFrom:from to:to];
			} else if ([keyPath isEqualToString:@"internalTotal"]) {
				[[object account] retotalFrom:[object date] to:nil];
			}
		}
	} else if (context == CBTransactionMutationChangeContext) {
		NSSet *added = [change objectForKey:NSKeyValueChangeNewKey];
		NSSet *removed = [change objectForKey:NSKeyValueChangeOldKey];
		NSNumber *kind = [change objectForKey:NSKeyValueChangeKindKey];
		
		if (added && (id)added != [NSNull null]) {
			if ([kind intValue] != NSKeyValueChangeSetting) {
				NSDate *from = nil;
				NSDate *to = nil;
				if ([added count] == 1) {
					CBTransaction <CBTransaction> *transaction = [added anyObject];
					if ([transaction internalTotal] == 0) {
						from = to = [transaction date]; // only retotal this one transaction for speed
					} else {
						from = [transaction date];
					}			
				} else {
					CBTransaction *transaction;
					NSEnumerator *enumerator = [added objectEnumerator];
					while (transaction = [enumerator nextObject]) {
						from = (from ? [from earlierDate:[transaction date]] : [transaction date]);
					}
				}
				[self retotalFrom:from to:to];
			}
			
			[self addTransactionObservers:added];
		}

		if (removed && (id)removed != [NSNull null]) {
			CBTransaction *transaction;
			NSDate *from = nil;
			NSEnumerator *enumerator = [removed objectEnumerator];
			while (transaction = [enumerator nextObject]) {
#warning this hasnt been checked
				NSDate *compareTo = [transaction date];
				// if from doesn't exist or if compareTo is smaller than from, set from to compareTo
				if (!from || ([from compare:compareTo] == NSOrderedDescending)) { from = compareTo; }
			}
			[self retotalFrom:from to:nil];
			[self removeTransactionObservers:removed];
		}
	}
}

- (void)retotalFrom:(NSDate *)from to:(NSDate *)to {
	if ([self isDeleted]) { return; }
	
	FSDLog(@"retotaling from %@ to %@", from, to);
	NSFetchRequest *request;
	NSArray *results;
	CBTransaction <CBTransaction> *transaction, *previousTransaction = nil;
	NSError *error = nil;
	NSEnumerator *transactionEnumerator;
	NSPredicate *basePredicate, *predicate;
	NSMutableArray *predicates;
	
	// the goal is to find all transactions in this account that have a logical order value between from and to
	// (as well as one just before from). the base predicate for that includes the following:
	//   1. the transaction's account for that value must be this account
	//   2. the value must be referring to the logical ordering attribute
	// the following predicate, therefore, is the base predicate for finding all values (and transactions from
	// those values) needed
	basePredicate = [NSPredicate predicateWithFormat:@"(account = %@)", self];

	// get the transaction before the ones that need totaling
	predicates = [NSMutableArray array];
	[predicates addObject:basePredicate]; // base predicate is needed
	if (from) {
		[predicates addObject:[NSPredicate predicateWithFormat:@"(date < %@)", from]];
		predicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicates];
		request = [[[NSFetchRequest alloc] init] autorelease];
		[request setEntity:[CBTransaction entity]];
		[request setPredicate:predicate];
		[request setSortDescriptors:[NSArray arrayWithObject:
									 [[[NSSortDescriptor alloc] initWithKey:@"date"
																  ascending:NO] autorelease]]];
		[request setFetchLimit:1];
		results = [[self managedObjectContext] executeFetchRequest:request error:&error];
		previousTransaction = [results count] ? [results objectAtIndex:0] : nil;
	}
		
	// get the transactions that need totaling
	predicates = [NSMutableArray array];
	[predicates addObject:basePredicate]; // base predicate is needed
	if (from) { [predicates addObject:[NSPredicate predicateWithFormat:@"(date >= %@)", from]]; }
	if (to) { [predicates addObject:[NSPredicate predicateWithFormat:@"(date <= %@)", to]]; }
	predicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicates];
	request = [[[NSFetchRequest alloc] init] autorelease];
	[request setEntity:[CBTransaction entity]];
	[request setPredicate:predicate];
	[request setSortDescriptors:[NSArray arrayWithObjects:
								 [[[NSSortDescriptor alloc] initWithKey:@"date"
															  ascending:YES] autorelease],
								 [[[NSSortDescriptor alloc] initWithKey:@"uniqueIdentifier"
															  ascending:YES] autorelease], nil]];
	results = [[self managedObjectContext] executeFetchRequest:request error:&error];
	
	// do some totaling
	[self willChangeValueForKey:@"total"];
	transactionEnumerator = [results objectEnumerator];
	while (transaction = [transactionEnumerator nextObject]) {
		long long total = [transaction internalTotal];
		if (previousTransaction) { total += [previousTransaction total]; }
		[transaction setTotal:total];
		previousTransaction = transaction;
	}
	[self didChangeValueForKey:@"total"];
}

- (long long)total {
	NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
	[request setEntity:[CBTransaction entity]];
	[request setPredicate:[NSPredicate predicateWithFormat:@"account = %@", self]];
	[request setSortDescriptors:[NSArray arrayWithObjects:
								 [[[NSSortDescriptor alloc] initWithKey:@"date"
															  ascending:NO] autorelease],
								 [[[NSSortDescriptor alloc] initWithKey:@"uniqueIdentifier"
															  ascending:NO] autorelease], nil]];
	[request setFetchLimit:1];
	NSArray *results = [[self managedObjectContext] executeFetchRequest:request error:NULL];
	return [[results firstObject] total];
}

@end
